#!/usr/bin/env python3
# 导入必要的模块
from calendar import c
import sys  # 系统模块，用于程序退出等操作
from pathlib import Path  # 路径处理模块
from typing import List, Optional  # 类型提示模块
import argparse  # 命令行参数解析模块
from utils.docker_manager import DockerManager, DockerConfig
from utils.ssh_client import Client
import traceback


class DeployConfig:
    """部署配置类，用于存储所有部署相关的配置信息
    
    属性说明：
        project_name: 项目名称，必填项
        project_alias: 项目别名，可选，用于区分同一项目的不同配置实例
        git_branch: Git 分支名称，可选，用于构建镜像名称
        image_tag: Docker 镜像标签，可选
        image: 完整的 Docker 镜像名称，可选，优先级高于分支和标签组合
        docker_run: Docker run 命令参数，必填项
        hosts: 部署目标主机列表
        harbor_registry: Harbor 镜像仓库地址
    """
    def __init__(self, project_name: str, docker_run: str, hosts: List[str],
                 harbor_registry: str, project_alias: Optional[str] = None,
                 git_branch: Optional[str] = None, image_tag: Optional[str] = None,
                 image: Optional[str] = None):
        # 必填参数
        self.project_name = project_name
        self.docker_run = docker_run
        self.hosts = hosts
        self.harbor_registry = harbor_registry
        
        # 可选参数
        self.project_alias = project_alias
        self.git_branch = git_branch
        self.image_tag = image_tag
        self.image = image
        
        # 验证必填参数
        self._validate()
    
    def _validate(self) -> None:
        """验证配置参数的有效性"""
        if not self.project_name:
            raise ValueError("项目名称不能为空")
        if not self.docker_run:
            raise ValueError("Docker run 命令不能为空")
        if not self.hosts:
            raise ValueError("部署主机列表不能为空")
        if not self.harbor_registry:
            raise ValueError("Harbor 镜像仓库地址不能为空")

    @classmethod
    def from_args(cls, args: argparse.Namespace) -> 'DeployConfig':
        """从命令行参数创建配置实例
        
        Args:
            args: 解析后的命令行参数对象
            
        Returns:
            DeployConfig: 配置对象实例
            
        Raises:
            ValueError: 当必填参数缺失时抛出
        """
        hosts = args.hosts.split(',') if args.hosts else []
        if not hosts:
            raise ValueError("部署脚本错误: hosts 不能为空")

        return cls(
            project_name=args.project_name,
            project_alias=args.project_alias,
            git_branch=args.git_branch,
            image_tag=args.image_tag,
            image=args.image,
            docker_run=args.docker_run,
            hosts=hosts,
            harbor_registry=args.harbor_registry
        )

    def __str__(self) -> str:
        """返回配置对象的字符串表示"""
        return f"DeployConfig(project='{self.project_name}', alias='{self.project_alias or 'None'}', hosts={len(self.hosts)})"

    def __repr__(self) -> str:
        """返回配置对象的详细字符串表示"""
        return (f"DeployConfig(project_name='{self.project_name}', "
                f"project_alias='{self.project_alias}', "
                f"git_branch='{self.git_branch}', "
                f"image_tag='{self.image_tag}', "
                f"image='{self.image}', "
                f"docker_run='{self.docker_run}', "
                f"hosts={self.hosts}, "
                f"harbor_registry='{self.harbor_registry}')")  


class DockerDeployer:
    """Docker 部署管理器，负责执行具体的部署操作"""

    def __init__(self, config: DeployConfig):
        """初始化部署管理器
        
        Args:
            config: 部署配置对象
        """
        self.config = config
        self.docker_image = self._get_docker_image()

    def _get_docker_image(self) -> Optional[str]:
        """获取 Docker 镜像名称"""
        if self.config.image:
            return self.config.image
        if self.config.git_branch and self.config.image_tag:
            return f"{self.config.harbor_registry}/{self.config.project_name}/{self.config.git_branch}:{self.config.image_tag}"
        return None

    def print_deploy_info(self) -> None:
        """打印部署信息
        
        输出所有相关的部署配置信息，包括项目信息、镜像信息、
        部署目标和卷配置等
        """
        print(f"项目名称: {self.config.project_name}")
        if self.config.project_alias:
            print(f"项目别名: {self.config.project_alias}")
        print(f"部署分支: {self.config.git_branch or '无'}")
        if self.docker_image:
            print(f"部署镜像: {self.docker_image}")
        print(f"部署主机: {','.join(self.config.hosts)}")

    def deploy_to_host(self, ssh_client: Client) -> None:
        try:
            print(f"\n{'=' * 20} {ssh_client.config.hostname} {'=' * 20}")
            docker_config = DockerConfig(
                registry_url=self.config.harbor_registry
            )
            docker_manager = DockerManager(ssh_client, docker_config)
    
            container_name = self.config.project_alias or self.config.project_name
    
            # 停止和删除旧容器
            print(f"停止并删除旧容器: {container_name}")
            docker_manager.remove_container(container_name, force=True)
            
            # 获取要使用的镜像名称
            if self.config.image:
                # 1. 如果指定了完整镜像名称，直接使用
                current_image = self.config.image
                print(f"使用指定镜像: {current_image}")
                docker_manager.pull_image(current_image)
            elif self.config.git_branch and self.config.image_tag:
                # 2. 如果指定了分支和标签，拼接镜像名称
                current_image = f"{self.config.harbor_registry}/{self.config.project_name}/{self.config.git_branch}:{self.config.image_tag}"
                print(f"拉取镜像: {current_image}")
                docker_manager.pull_image(current_image)
            else:
                # 3. 都没有指定，查找远程主机上最新的镜像
                current_image = docker_manager.get_latest_image(self.config.project_name)
                if not current_image:
                    raise Exception(f"未找到项目 {self.config.project_name} 的任何镜像，请使用 --git_branch 和 --image_tag 或 --image 指定镜像")
                print(f"使用远程主机最新镜像: {current_image}")
            
            # 准备 docker run 命令
            docker_run = f"{self.config.docker_run} \
                --name {container_name} \
                {current_image}"
            print(f"容器启动命令:\n{docker_run}")
    
            # 启动新容器
            print(f"启动新容器: {container_name}")
            print(docker_run)
            docker_manager.start_container(container_name, docker_run)
    
            # 清理旧镜像
            if self.config.git_branch or self.config.image:
                print("清理旧镜像")
                docker_manager.clean_images(self.config.project_name)
    
            print(f"{'=' * 20} 部署成功 {'=' * 20}\n")
    
        except Exception as e:
            print(f"在主机 {host} 上部署失败: {str(e)}")
            traceback.print_exc()
            raise

    def deploy(self, ssh_clients: List[Client]) -> None:
        """执行完整的部署流程
        
        遍历所有目标主机进行部署，并汇总部署结果
        """
        print("\n==================== Docker 部署开始 ====================")
        self.print_deploy_info()
        print("\n==================== 开始部署到各个主机 ====================")
        
        failed_hosts = []  # 记录部署失败的主机
        for ssh_client in ssh_clients:
            try:
                self.deploy_to_host(ssh_client)
            except Exception as e:
                failed_hosts.append((ssh_client.config.hostname, str(e)))
                continue
            finally:
                if 'ssh_client' in locals():
                    ssh_client.close()

        # 输出部署结果汇总
        print("\n==================== 部署结果汇总 ====================")
        if not failed_hosts:
            print("所有主机部署成功！")
        else:
            print("以下主机部署失败:")
            for host, error in failed_hosts:
                print(f"  - {host}: {error}")
            sys.exit(1)


def parse_args() -> argparse.Namespace:
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description="Docker 项目部署脚本",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    
    # 添加命令行参数
    parser.add_argument('-p', '--project_name', required=True, help='项目名称')
    parser.add_argument('--project_alias', help='项目别名，用于区分同一项目的不同配置实例')
    parser.add_argument('--git_branch', help='Git 分支名称')
    parser.add_argument('--image_tag', help='Docker 镜像标签')
    parser.add_argument('--image', help='完整的 Docker 镜像名称（优先级高于分支和标签）')
    parser.add_argument('--docker_run', required=True, help='Docker run 命令（不含镜像名）')
    parser.add_argument('--hosts', required=True, help='部署主机列表（逗号分隔）')
    parser.add_argument('--harbor_registry', default='harbor.labworlds.cc', help='Harbor 镜像仓库地址')

    return parser.parse_args()


if __name__ == '__main__':
    """主函数
    
    处理整个部署流程，包括参数解析、配置创建和部署执行
    """
    try:
        args = parse_args()  # 解析命令行参数
        config = DeployConfig.from_args(args)  # 创建配置对象
        deployer = DockerDeployer(config)  # 创建部署器
        
        # 自定义ssh
        # 为每个主机创建 SSH 客户端
        ssh_clients = []
        for host in deployer.config.hosts:
            ssh_client = Client(
                hostname=host,
                port=5423,  # SSH 默认端口
                username='ubuntu',  # 默认用户名
                private_key_file='E:/ssh-key/school_linux_key/id_rsa'  # 默认私钥路径
            )
            ssh_clients.append(ssh_client)
        deployer.deploy(ssh_clients)  # 执行部署
    except KeyboardInterrupt:
        print("\n部署被用户中断")
        sys.exit(1)
    except Exception as e:
        print(f"部署失败: {str(e)}", file=sys.stderr)
        sys.exit(1)